﻿/*******************************************************************************
 * Level Master 2000
 * Copyright (C) 2010 Christian Herzog
 * For more information see http://code.google.com/p/levelmaster2000/
 *
 * This file is licensed under the terms of the MIT license, see
 * http://www.opensource.org/licenses/mit-license.php for further information.
 ******************************************************************************/

package com.levelmaster2000
{
    import com.levelmaster2000.Selection;
    import com.levelmaster2000.TransformGizmo;
    import com.pblabs.box2D.Box2DDebugComponent;
    import com.pblabs.engine.core.*;
    import com.pblabs.engine.core.LevelManager;
    import com.pblabs.engine.debug.Console;
    import com.pblabs.engine.debug.Logger;
    import com.pblabs.engine.entity.*;
    import com.pblabs.engine.entity.IEntity;
    import com.pblabs.engine.PBE;
    import com.pblabs.engine.serialization.Serializer;
    import com.pblabs.rendering2D.*;
    import com.pblabs.rendering2D.DisplayObjectScene;
    import com.pblabs.rendering2D.ui.*;
    import flash.display.*;
    import flash.display.Sprite;
    import flash.events.*;
    import flash.external.ExternalInterface;
    import flash.geom.Point;
    import flash.net.LocalConnection;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.ui.Mouse;
    import flash.utils.*;
    
    import flash.system.Security;
    
    [SWF(width="800", height="600", frameRate="60", backgroundColor="#08d4f8")]
    public class EditorMain extends Sprite
    {      
        private const DOUBLE_CLICK_INTERVAL:int = 400;
        private var clickIntervalTimer:int;
        
        private static const CURSOR_SIZE:int = 16;
        private static const ICON_CURSOR_HAND_OPEN:String   = "../assets/icons/cursor-openhand.png";
        private static const ICON_CURSOR_HAND_CLOSED:String = "../assets/icons/cursor-closedhand.png";
        private static const ICON_CURSOR_ZOOM:String        = "../assets/icons/cursor-zoom.png";
        
        private var _resources:Resources;
        private var conn:LocalConnection;
        public var selection:Selection = new Selection();
        private var logListener:EditorLogger = new EditorLogger();
        
        private var isRunning:Boolean = false;

        private var communicator:Communicator = null;
        
        public var componentInfo:Dictionary = new Dictionary();;
        
        private var lastMouseCoords:Point = new Point();
        private var isInNavigationMode:Boolean = false;
        private var isMouseButtonDown:Boolean = false;

		private var nextLevelToLoad:int = -1;
        private var loadNextLevel:Boolean = false;

		
        public static var isInTransformationMode:Boolean = false;
        
        private var _editorGroup:PBGroup;
        public function get editorGroup():PBGroup
        {
            return _editorGroup;
        }
        
        private var cursor:SpriteRenderer;
        
        public function EditorMain()
        {
            EditorHelper.editor = this;
            
            communicator = new Communicator(this);

            Security.allowDomain("*");

            if (this.stage)
            {
                onAddedToStage();
            } 
            else 
            {
                addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            }
        }
		
        public function onEnterFrame(ev:Event):void
        {
            //--------------------------------------------------------
            //camera movement:
            //--------------------------------------------------------
            if (isRunning)
            {
            if(PBE.isKeyDown(InputKey.SPACE))
            {
                Mouse.hide();  
                
                isInNavigationMode = true;
                
                cursor.position = PBE.scene.transformScreenToWorld(
                    new Point(PBE.mainStage.mouseX, PBE.mainStage.mouseY));
                    
                cursor.alpha = 1.0;

                var dX:int = lastMouseCoords.x - PBE.mainStage.mouseX;
                var dY:int = lastMouseCoords.y - PBE.mainStage.mouseY;
                
                //Zoom:
                if(PBE.isKeyDown(InputKey.CONTROL))
                {
                    cursor.fileName = ICON_CURSOR_ZOOM;
                    
                    if(isMouseButtonDown)
                    {
                        PBE.scene.zoom += 0.001 * dY;

                        cursor.size= new Point(CURSOR_SIZE/PBE.scene.zoom, CURSOR_SIZE/PBE.scene.zoom);
                    }
                }
                //Pan:
                else
                {
                    if(isMouseButtonDown)
                    {
                        PBE.scene.screenPan(dX, dY);
                        
                        cursor.fileName = ICON_CURSOR_HAND_CLOSED;
                    }
                    else
                    {
                        cursor.fileName = ICON_CURSOR_HAND_OPEN;
                    }
                }
            }
            else
            {
                Mouse.show();

                isInNavigationMode = false;
                
                if(cursor)
                {
                    cursor.alpha = 0.0;
                }
            }
            //--------------------------------------------------------
            //camera movement end.
            //--------------------------------------------------------
            
            //Update the last mouse coordinates:
            lastMouseCoords.x = PBE.mainStage.mouseX;
            lastMouseCoords.y = PBE.mainStage.mouseY;
            }
        }        
        
        public function OnSchemaReceived(type:String, data:String):void
        {
            switch( type )
            {
                case "START":
                {
                    PBE.processManager.timeScale = 0.0;
                }
                break;
                
                case "END":
                {
					//componentInfo is now filled with precious data :)
                    PBE.processManager.timeScale = 1.0;
                    onReady();
                }
                break;
                
                case "ERROR":
                {
                
                }
                break;
                
                case "TYPE":
                {
                    var myXML:XML = new XML(data);
                    myXML.ignoreWhite = true;
                    
                    var xmlList:XMLList = myXML.child("factory");
                    var classType:String = xmlList.@type;
                    componentInfo[classType] = new Dictionary();
                    
                    var edData:EditorData = null; 
                    
                    //Check accessors for editor meta data:
                    for each (var acc:XML in xmlList.accessor)
                    {
                        edData = checkEditorData( acc );

                        if ( !edData.ignore )
                        {
                            //Logger.print(this, acc.@name + ":\t" + acc.@type );
                            componentInfo[classType][acc.@name.toString()] = acc.@type.toString();
                        }
                    }
                    
                    //Check public variables for editor meta data:
                    for each (var pVar:XML in xmlList.variable)
                    {
                        edData = checkEditorData( pVar );

                        if ( !edData.ignore )
                        {
                            //Logger.print(this, pVar.@name + ":\t" + pVar.@type );
                            componentInfo[classType][pVar.@name.toString()] = pVar.@type.toString();
                        }
                    }
                }
                break;
            }
        }
        
        //TODO: use TypeUtility.getEditorData instead? See Serializer.serializeProperty for usage.
        private static function checkEditorData(property:XML):EditorData
        {
            var edData:EditorData = new EditorData();
            
            if( property.children().toString() != "" )
            {
                for each (var metadata:XML in property.metadata)
                {
                    for each(var arg:XML in metadata.arg)                                
                    {
                        switch(arg.@key.toString())
                        {
                            case "ignore":
                            {
                                switch( arg.@value.toString() )
                                {
                                    case "true":
                                    edData.ignore = true;
                                    break;
                                    
                                    case "false":
                                    edData.ignore = false;
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }
            }
            
            return edData;
        }
        
        private function doBuildComponentInfo():void
        {
            conn = new LocalConnection();
            conn.client = this;
            conn.allowDomain('*'); //TODO: fix domain!
            try 
			{
                conn.connect("_SchemaConnection");
            } 
			catch (error:ArgumentError) 
			{
               Logger.print(this, "Can't connect to _SchemaConnection");
            }
            SchemaGenerator.instance.generateSchema();
        }
        
      
        
        //Called after SchemaGenerator has finished generating Schema.
        private function onReady():void
        {
            isRunning = true;
            (PBE.scene as DisplayObjectScene).maxZoom = 2.0;
            
            initMouseCursors();
            
            PBE.mainStage.addEventListener(MouseEvent.MOUSE_DOWN, OnClick); 
            PBE.mainStage.addEventListener(MouseEvent.MOUSE_UP, OnMouseUp);
            
            if(ExternalInterface.available) 
            {
                try 
                {
                    ExternalInterface.addCallback("sendToActionScript", communicator.receivedFromJavaScript);
                    if (communicator.checkJavaScriptReady()) 
                    {
                        Logger.print(this, "JavaScript is ready");
                    } 
                    else 
                    {
                        Logger.print(this, "JavaScript is not ready, creating timer.");
                    }
                } 
                catch (error:SecurityError) 
                {
                    Logger.error(this, "onReady", "A SecurityError occurred: " + error.message);
                } 
                catch (error:Error) 
                {
                    Logger.error(this, "onReady", "An Error occurred: " + error.message);
                }
            } 
            else 
            {
                Logger.error(this, "onReady", "External interface is not available for this container.");
            }   
            
            communicator.sendComponentClasses();
			
            //Add level loaded callback:
            LevelManager.instance.addEventListener(LevelEvent.LEVEL_LOADED_EVENT, onLevelLoaded);
            LevelManager.instance.addEventListener(LevelEvent.LEVEL_UNLOADED_EVENT, onLevelUnloaded);
        }
        
        
        private var mLoader:Loader;
        public function onCompleteHandler(loadEvent:Event):void
        {
            addChild(loadEvent.currentTarget.content);

            Logger.registerListener(logListener);
			
			Logger.print(this, "onCompleteHandler - external SWF LOADED!");
            
			doBuildComponentInfo();
		}

        private function initMouseCursors():void
        {
            var oldGroup:PBGroup = PBE.currentGroup;

            _editorGroup = new PBGroup();
            _editorGroup.initialize("EditorEntities");
            _editorGroup.owningGroup = PBE.rootGroup;

            PBE.currentGroup = _editorGroup;

            var cursorEntity:IEntity = PBE.allocateEntity();
            cursorEntity.initialize("CursorEntity");
             
            cursor = new SpriteRenderer();
            cursor.scene = PBE.scene;
            cursor.fileName = ICON_CURSOR_HAND_OPEN;
            cursor.position = new Point(0, 0);
            cursor.rotation = 0.0;
            cursor.size = new Point(CURSOR_SIZE, CURSOR_SIZE);
            cursor.alpha = 0.0;
            cursor.zIndex = 255;
            
            cursorEntity.addComponent( cursor, "Render" );        
            (cursor.displayObject as Sprite).mouseEnabled = true;
            
            var transformGizmoEntity:IEntity = PBE.allocateEntity();
            transformGizmoEntity.initialize("TransformGizmoEntity");

            var gizmoComponent:TransformGizmo = new TransformGizmo();
            transformGizmoEntity.addComponent( gizmoComponent, "TransformGizmo" );        

            PBE.currentGroup = oldGroup;
        }
                 
        public function onLevelLoaded(event:LevelEvent):void
        {
           Logger.print(this, "ON LOADED LEVEL");

            communicator.sendSceneHierarchy();
        }
        
        
        public function onLevelUnloaded(event:LevelEvent):void
        {
			clearScene();
            if(loadNextLevel)
            {
                loadNextLevel = false;
                LevelManager.instance.loadLevel(nextLevelToLoad, true);
				nextLevelToLoad = -1;
            }
        }

        private function OnMouseUp(e:MouseEvent):void
        {
            isMouseButtonDown = false;
        }

        public function loadLevel(index:int):void
        {
            selection.clear();
            
            if(LevelManager.instance.currentLevel > 0)
            {
				//Just set the nextLevelToLoad and unload current.
				//The rest is done in onLevelUnloaded callback.
                nextLevelToLoad = index;
                loadNextLevel = true;
                LevelManager.instance.unloadCurrentLevel();
            }
            else
            {
                LevelManager.instance.loadLevel(index);
            }
        }
        
        public function setEntityXml(name:String, eXml:XML):void
        {
            var entity:IEntity = PBE.nameManager.lookup(name);
            
            if(entity != null)
            {
                entity.destroy();
                entity = PBE.allocateEntity();
                entity.initialize(name);
                entity.deserialize(eXml.children()[0]);
            }
            else
            {
                Logger.warn(this, "setEntityXml", "Entity " + name + " not found. Creating...");
                entity = PBE.allocateEntity();
                entity.initialize(name);
                entity.deserialize(eXml.children()[0]);
            }
            
            Serializer.instance.resolveReferences();
        }
        
        public function setComponentXml(entityName:String, componentName:String, componentXML:XML):void
        {
            var entity:IEntity = PBE.nameManager.lookup(entityName);
            
            if(entity != null)
            {
                var component:IEntityComponent = entity.lookupComponentByName(componentName);
                if (component != null)
                {
                    Serializer.instance.setCurrentEntity(entity);
                    Serializer.instance.deserialize(component, componentXML);
                }
            }
            
            Serializer.instance.resolveReferences();
        }
        
        public function getComponentXml(entityName:String, componentName:String):XML
        {
            var xml:XML = <ERROR />;

            var entity:IEntity = PBE.nameManager.lookup(entityName);
            
            if(entity is IEntity)
            {
                var component:IEntityComponent = entity.lookupComponentByName(componentName);
                if (component == null)
                {
                    Logger.error(this, "getComponentXml", componentName + " not found.");
                    return xml;
                }
                
                var componentXML:XML = <component type={getQualifiedClassName(component).replace(/::/,".")} name={component.name} />;
                
                Serializer.instance.setCurrentEntity(entity);
                Serializer.instance.serialize(component, componentXML);

                return componentXML;
            }
            else
            {
                Logger.error(this, "getComponentXml", entityName + " is not found or an entity.");
            }
            
            return xml;
        }
        
        public function getEntityXml(name:String):XML
        {
            var eXml:XML = <things />;
            var entity:IEntity = PBE.nameManager.lookup(name);
            
            if(entity is IEntity)
            {
                entity.serialize(eXml);
            }
            else
            {
                Logger.error(this, "getEntityXml", name + " is not found or an entity.");
            }
            
            return eXml;
        }
        
        /**
         * Removes all entities from the scene, except the scene entity
         * and editor relevant stuff.
         */
        public function clearScene():void
		{
            for each(var object:PBObject in PBE.nameManager.objectList)
            {
                if(object is PBGroup)
                {
                    var group:PBGroup = object as PBGroup;
                    if (group != _editorGroup
                        && group != PBE.rootGroup)
                    {
                        group.destroy();
                    }
                }
				
                //Needed, as rootgroup must not be deleted, but entities can lie within it.
                //Also, don't delete the scene entity.
                else if(object is IEntity)
                {
                    var entity:IEntity = object as IEntity;
                    if(entity.owningGroup != _editorGroup
                        && entity.lookupComponentByType(IScene2D) == null
                        && entity.lookupComponentByType(ISpatialManager2D) == null
						/*&& entity.lookupComponentByType(Box2DDebugComponent) == null*/)
                    {
                        entity.destroy();
                    }
                }
            }
        }
        
        
        private function OnClick(e:MouseEvent):void
        {
            if(isInNavigationMode)
            {
                isMouseButtonDown = true;
                return;
            }
            
            if(isInTransformationMode)
            {
                return;
            }
            
            
            if((getTimer() - clickIntervalTimer) < DOUBLE_CLICK_INTERVAL) 
            {
                
                //--------------------------------------------------------
                //selected something:
                //--------------------------------------------------------
                //selection.clear();//selectedEntities = new Array();
                var selectedDisplayObjects:Array = new Array();
       
                var point:Point = new Point(PBE.mainStage.mouseX, PBE.mainStage.mouseY);

                //TODO: compare with "old" selection, in case it's the same, cycle through selection each click
                var objs:Boolean = PBE.scene.getRenderersUnderPoint(point, selectedDisplayObjects);

                var selectedEntities:Array = new Array();
                for each(var obj:DisplayObjectRenderer in selectedDisplayObjects)
                {
					if(obj.owner.lookupComponentByType(Box2DDebugComponent) != null)
					{
						continue;
					}
					
                    if(obj.owner.owningGroup != _editorGroup)
                    {
                        selectedEntities.push(obj.owner);
                    }                   
                }
                
                selection.addMultipleToSelection(selectedEntities, true);
                
                communicator.sendRequestSelectEntity();
            }

            clickIntervalTimer = getTimer();
        }	         
		
        
        private function onAddedToStage(evt:Event = null):void 
        {
			mLoader = new Loader();
            var mRequest:URLRequest = new URLRequest("Demo.swf");
            
            mLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onCompleteHandler);
            var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
            mLoader.load(mRequest, context);
            
            removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);

            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
         
			Console.registerCommand("save", onSerializePBE);
			
			//onCompleteHandler is called next...
		}
		
        public function saveCurrentLevel():XML
        {
            return EditorHelper.serializePBE();
        }
        
		public function onSerializePBE():void
		{
			var xml:XML = EditorHelper.serializePBE();
			//Logger.print(this, xml.toXMLString());
		}
        
    }
}
